package org.flashamaphone.tones
{
	/** 
	 * A set of several notes which accepts a chord notation as input to define the set
	 */
	public class Chord extends Polynote
	{
		public static const MAJOR_TRIAD:String = "maj";
		public static const MINOR_TRIAD:String = "m";
		public static const SEVENTH:String = "7";
		public static const MINOR_SEVENTH:String = "m7";
		public static const MAJOR_SEVENTH:String = "maj7";
		public static const NINTH:String = "9";
		public static const MINOR_NINTH:String = "m9";
		public static const MAJOR_NINTH:String = "maj9";
		public static const ELEVENTH:String = "11";
		public static const THIRTEENTH:String = "13";
		public static const SIXTH:String = "6";
		public static const MINOR_SIXTH:String = "m6";
		
		public static const SUSTAIN:String = "sus";
		public static const AUGMENTED:String = "aug";
		public static const DIMINISHED:String = "dim";
		
		/** root note of chord */
		protected var _root:String;
		
		/** chord notation */
		protected var _notation:String;

		/** octave of root */
		protected var _rootOctave:int;
		
		/** chord modifier */
		protected var _modifier:String;
		
		/** inversion */
		protected var _inversion:int;
		
		/**
		 * constructor
		 * 
		 * @param notation of chord
		 * @param duration
		 * @param octave of root
		 */
		public function Chord(notation:String, duration:Number = 1, rootOctave:int=4)
		{
			_rootOctave = rootOctave;
			_notation = notation;
			buildChord();
			super(null, duration);
		}
		
		/**
		 * chord notation setter
		 * 
		 * @param notation
		 */
		public function set notation(value:String):void {
			_notation = value;
			buildChord();
		}
		
		/**
		 * chord notation getter
		 * 
		 * @return notation
		 */
		public function get notation():String {
			return _notation;
		}
		
		/**
		 * root note setter
		 * 
		 * @param root
		 */
		public function set root(value:String):void {
			_root = value;
			buildChord();
		}
		
		/**
		 * root note getter
		 * 
		 * @return root note
		 */
		public function get root():String {
			return _root;
		}
		
		/**
		 * root octave setter
		 * 
		 * @param octave
		 */
		public function set rootOctave(value:int):void {
			_rootOctave = value;
			buildChord();
		}
		
		/**
		 * root octave getter
		 * 
		 * @return root octave
		 */
		public function get rootOctave():int {
			return _rootOctave;
		}
		
		
		/**
		 * convert notation to note list
		 *
		 * @param notation
		 * @return note list
		 */
		public static function notesFromChordNotation(notation:String):Array {
			var root:String;
			var major:int = 0;
			var chordType:String;
			
			// find root
			if (notation.charAt(1) == "#" || notation.charAt(1) == "b") {
				root = notation.substring(0, 2);
				notation = notation.substring(2);
			} else {
				root = notation.substring(0, 1);
				notation = notation.substring(1);
			}
			
			// major or minor? (3 states - 1 is on, -1 is off, 0 is unspecified)
			if ( notation.substr(0, 3) == "maj") {
				major = 1;
				notation = notation.substring(3);
			} else if (notation.substr(0, 1) == "m") {
				major = -1;
				notation = notation.substring(1);
			}

			// set chord type
			if ( notation.charAt(0) == "6" ) {
				if (major == -1) {
					chordType = Chord.MINOR_SIXTH
				} else {
					chordType = Chord.SIXTH;
				}
				notation = notation.substring(2);
			} else if ( notation.charAt(0) == "7" ) {
				if (major == 0) {
					chordType = Chord.SEVENTH;
				} else if (major == 1) {
					chordType = Chord.MAJOR_SEVENTH;
				} else if (major == -1) {
					chordType = Chord.MINOR_SEVENTH;
				}
				notation = notation.substring(1);
			} else if ( notation.charAt(0) == "9" ) {
				if (major == 0) {
					chordType = Chord.NINTH;
				} else if (major == 1) {
					chordType = Chord.MAJOR_NINTH;
				} else if (major == -1) {
					chordType = Chord.MINOR_NINTH;
				}
				notation = notation.substring(1);
			} else if ( notation.substr(0,2) == "11" ) {
				chordType = Chord.ELEVENTH;
				notation = notation.substring(2);
			} else if ( notation.substr(0,2) == "13" ) {
				chordType = Chord.THIRTEENTH;
				notation = notation.substring(2);
			} else {
				if (major == 1 || major == 0) {
					chordType = Chord.MAJOR_TRIAD;
				} else {
					chordType = Chord.MINOR_TRIAD;
				}
			}
			
			var notes:Array = notesFromChordType(chordType, root);
			
			// modify note set if needed
			var modifier:String = notation;
			switch ( modifier.substr(0,3) ) {
				case Chord.AUGMENTED:
					notes = augment(notes);
					break;
				
				case Chord.DIMINISHED: 
					// to do
					break;
				
				case Chord.SUSTAIN:
					var param:int = int(modifier.charAt(3));
					notes = sustain(notes, param);
					break;
			}
			
			return notes;
		}
		
		/**
		 * get notes from chord types
		 * 
		 * @param type
		 * @param chord root
		 * @return notes
		 */
		public static function notesFromChordType(type:String, root:String):Array {
			switch ( type ) {
				case Chord.SIXTH:
					return Chord.sixth(root);
				
				case Chord.MINOR_SIXTH:
					return Chord.minorSixth(root);
				
				case Chord.SEVENTH:
					return Chord.seventh(root);
				
				case Chord.MINOR_SEVENTH:
					return Chord.minorSeventh(root);
				
				case Chord.MAJOR_SEVENTH:
					return Chord.majorSeventh(root);
				
				case Chord.NINTH:
					return Chord.ninth(root);
				
				case Chord.MINOR_NINTH:
					return Chord.minorNinth(root);
				
				case Chord.MAJOR_NINTH:
					return Chord.majorNinth(root);
				
				case Chord.ELEVENTH:
					return Chord.eleventh(root);
				
				case Chord.THIRTEENTH:
					return Chord.thirteenth(root);
				
				case Chord.MAJOR_TRIAD:
					return Chord.majorTriad(root);
					
				case Chord.MINOR_TRIAD:
					return Chord.minorTriad(root);

				default:
					return Chord.majorTriad(root);
			}
		}
		
		/**
		 * get notes in major triad
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function majorTriad(root:String):Array {
			return getStandardNotesInChordMakeup(root, true).slice(0,3);
		}
		
		/**
		 * get notes in minor triad
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function minorTriad(root:String):Array {
			return getStandardNotesInChordMakeup(root, false).slice(0,3);
		}
		
		/**
		 * get notes in seventh chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function seventh(root:String):Array {
			return getStandardNotesInChordMakeup(root, true, false).slice(0,4);
		}
		
		/**
		 * get notes in major seventh chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function majorSeventh(root:String):Array {
			return getStandardNotesInChordMakeup(root, true, true).slice(0,4);
		}
		
		/**
		 * get notes in minor seventh chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function minorSeventh(root:String):Array {
			return getStandardNotesInChordMakeup(root, false, true).slice(0,4);
		}
		
		/**
		 * get notes in ninth chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function ninth(root:String):Array {
			return getStandardNotesInChordMakeup(root, true, false).slice(0,5);
		}
		
		/**
		 * get notes in major ninth chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function majorNinth(root:String):Array {
			return getStandardNotesInChordMakeup(root, true, true).slice(0,5);
		}
		
		/**
		 * get notes in minor ninth chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function minorNinth(root:String):Array {
			return getStandardNotesInChordMakeup(root, false, true).slice(0,5);
		}
		
		/**
		 * get notes in eleventh chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function eleventh(root:String):Array {
			return getStandardNotesInChordMakeup(root, true, false).slice(0,6);
		}
		
		/**
		 * get notes in major eleventh chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function majorEleventh(root:String):Array {
			return getStandardNotesInChordMakeup(root, true, true).slice(0,6);
		}
		
		/**
		 * get notes in minor eleventh chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function minorEleventh(root:String):Array {
			return getStandardNotesInChordMakeup(root, false, true).slice(0,6);
		}
		
		/**
		 * get notes in thirteenth chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function thirteenth(root:String):Array {
			return getStandardNotesInChordMakeup(root, true, false).slice(0,7);
		}
		
		/**
		 * get notes in major thirteenth chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function majorThirteenth(root:String):Array {
			return getStandardNotesInChordMakeup(root, true, true).slice(0,7);
		}
		
		/**
		 * get notes in minor thirteenth chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function minorThirteenth(root:String):Array {
			return getStandardNotesInChordMakeup(root, false, true).slice(0,7);
		}
		
		
		/**
		 * get notes in sixth chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function sixth(root:String):Array {
			var keySig:Array = Note.notesInKeySignature(root, true);
			var keys:Array = new Array();
			keys.push(keySig[0], keySig[2], keySig[4], keySig[5]);
			return keys;
		}
		
		/**
		 * get notes in minor sixth chord
		 * 
		 * @param root note
		 * @return notes
		 */
		public static function minorSixth(root:String):Array {
			var keySig:Array = Note.notesInKeySignature(root, false);
			var keys:Array = new Array();
			keys.push(keySig[0], keySig[2], keySig[4], keySig[5]);
			return keys;
		}
		
		/**
		 * sustain chord
		 * 
		 * @param notes
		 * @param direction to sustain
		 * @return notes
		 */
		public static function sustain(notes:Array, sus:int = 4):Array {
			// grab the third in the chord
			var third:String = notes[1];
			var notations:Array = Note.sharpNotations;
			var thirdIndex:int = Note.sharpNotations.indexOf(third);
			if (thirdIndex == -1) {
				notations = Note.flatNotations;
				thirdIndex = Note.flatNotations.indexOf(third);
			}
			
			if (sus==2) {
				// lower the third one half step
				if (thirdIndex-1 < 0) {
					notes[1] = notations[notations.length-1];
				} else {
					notes[1] = notations[thirdIndex-1];
				}
			} else { // assume sus == 4
				// raise the third one half step
				if (thirdIndex+1 >= notations.length) {
					notes[1] = notations[0];
				} else {
					notes[1] = notations[thirdIndex+1];
				}
			}
			
			return notes;
		}
		
		/**
		 * augment chord
		 * 
		 * @param notes
		 * @return notes
		 */
		public static function augment(notes:Array):Array {
			// grab the fifth in the chord
			var fifth:String = notes[2];
			var notations:Array = Note.sharpNotations;
			var fifthIndex:int = Note.sharpNotations.indexOf(fifth);
			if (fifthIndex == -1) {
				notations = Note.flatNotations;
				fifthIndex = Note.flatNotations.indexOf(fifth);
			}
			
			// raise the fifth one half step
			if (fifthIndex+1 >= notations.length) {
				notes[2] = notations[0];
			} else {
				notes[2] = notations[fifthIndex+1];
			}
			return notes;
		}
		
		/**
		 * get all standard notes in a chord, from triad to thirteenth
		 * 
		 * @param root note
		 * @param major key (true/false)
		 * @param major chord (true/false)
		 * @return notes array
		 */
		protected static function getStandardNotesInChordMakeup(root:String, majorKey:Boolean = true, majorChord:Boolean = false):Array {
			var majKeySig:Array = Note.notesInKeySignature(root, true);
			var minKeySig:Array = Note.notesInKeySignature(root, false);
			var notes:Array;
			if (majorKey && majorChord) {
				// C Major Seventh for example
				notes = [ majKeySig[0], majKeySig[2], majKeySig[4], majKeySig[6], majKeySig[1], majKeySig[3] ];
			} else if (!majorKey && majorChord) {
				// C Minor Seventh for example
				notes = [ minKeySig[0], minKeySig[2], minKeySig[4], minKeySig[6], minKeySig[1], minKeySig[3] ];
			} else if (majorKey && !majorChord) {
				// C Seventh for example
				notes = [ majKeySig[0], majKeySig[2], majKeySig[4], minKeySig[6], majKeySig[1], minKeySig[3] ];
			} else if (!majorKey && !majorChord) {
				// C Seventh for example
				notes = [ majKeySig[0], minKeySig[2], majKeySig[4], minKeySig[6], majKeySig[1], minKeySig[3] ];
			}
			return notes;
		} 
		
		/**
		 * build the chord given the parameters set in this class
		 */
		protected function buildChord():void {
			var notations:Array = notesFromChordNotation(_notation);
			var currentOctave:int = this.rootOctave;
			var lastAddedNote:Note;
			for each (var notation:String in notations) {
				// test frequency of new note, and make sure we're going up,
				// even if it means going into a new octave
				var newnote:Note = new Note(notation, currentOctave);
				if (lastAddedNote && newnote.frequency < lastAddedNote.frequency) {
					currentOctave++;
				}
				lastAddedNote = addNote(notation, currentOctave);
			}
		}
		
	}
}